package qc.module.qms.api.controller;

import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.client.RestTemplate;
import qc.common.core.constants.QCAuthConstant;
import qc.common.core.enums.ColumnTypeEnum;
import qc.common.core.enums.DataTypeEnum;
import qc.common.core.enums.TimeRangeLengthEnum;
import qc.common.core.exception.QCPromptException;
import qc.common.core.json.JsonParserUtil;
import qc.common.core.pair.KeyValuePairDto;
import qc.common.core.table.QCTable;
import qc.common.core.table.QCTableCell;
import qc.common.core.table.QCTableRow;
import qc.common.core.utils.DateUtil;
import qc.common.core.utils.NumberUtil;
import qc.common.core.utils.StringSplitUtil;
import qc.module.platform.dto.dbTable.DBTableFullDto;
import qc.module.platform.dto.dbTableColumn.DBTableColumnDto;
import qc.module.platform.dto.dbTableData.DBTableDataOperateDto;
import qc.module.platform.dto.dbTableSql.DBTableSqlDto;
import qc.module.platform.dto.dbTableSql.DBTableSqlSimpleDto;
import qc.module.platform.dto.dept.DeptSimpleDto;
import qc.module.qms.dto.exchange.category.ExchangeCategoryDto;
import qc.module.qms.dto.exchange.channel.ExchangeChannelDto;
import qc.module.qms.dto.exchange.config.ExchangeCategoryItemDto;
import qc.module.qms.dto.exchange.config.ExchangeChannelItemDto;
import qc.module.qms.dto.exchange.config.ExchangeConfigQueryConditionDto;
import qc.module.qms.dto.exchange.config.ExchangeConfigQueryResultDto;
import qc.module.qms.dto.exchange.data.*;
import qc.module.qms.dto.station.StationDto;
import qc.module.qms.entity.ExchangeCategoryStation;
import qc.module.qms.service.*;

import javax.servlet.http.HttpServletRequest;
import java.sql.SQLException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 数据交换数据管理Controller，用于查询和管理交换数据
 *
 * @author QuCheng Tech
 * @since 2024/1/23
 */
@RestController
@RequestMapping("/exchdata")
public class ExchangeDataController {
    private ExchangeChannelService channelService;
    private ExchangeCategoryService categoryService;
    private QmsStationService stationService;
    private ExchangeCategoryStationService categoryStationService;
    private ExchangeDataService exchangeDataService;

    @Autowired
    public void setExchangeChannelService(ExchangeChannelService channelService) {
        this.channelService = channelService;
    }

    @Autowired
    public void setExchangeCategoryService(ExchangeCategoryService categoryService) {
        this.categoryService = categoryService;
    }

    @Autowired
    public void setExchangeStationService(QmsStationService stationService) {
        this.stationService = stationService;
    }

    @Autowired
    public void setExchangeCategoryStationService(ExchangeCategoryStationService categoryStationService) {
        this.categoryStationService = categoryStationService;
    }

    @Autowired
    public void setExchangeDataService(ExchangeDataService exchangeDataService) {
        this.exchangeDataService = exchangeDataService;
    }

    @Autowired
    HttpServletRequest request;

    @Autowired
    private RestTemplate restTemplate;

    @Autowired
    private DiscoveryClient discoveryClient;

    /***
     * 根据指定输入得到查询交换数据条件选择输入数据
     *
     * @param condition 查询输入条件
     * @return qc.module.qms.dto.exchange.config.ExchangeConfigQueryResultDto
     * @author QuCheng Tech
     * @since 2024/1/24
     */
    @RequestMapping(value = "/condition", method = {RequestMethod.POST, RequestMethod.GET})
    public ExchangeConfigQueryResultDto getQueryConditionData(@RequestBody ExchangeConfigQueryConditionDto condition) throws QCPromptException {
        String channelIdString = null;
        String categoryIdString = null;
        String stationCodeString = null;

        if (condition != null) {
            channelIdString = condition.getChannel();
            categoryIdString = condition.getCategory();
            stationCodeString = condition.getStcd();
        }

        return getExchangeConfigQueryResult(channelIdString, categoryIdString, stationCodeString);
    }

    /***
     * 根据指定输入得到查询交换数据条件选择输入数据，使用url中进行传值
     *
     * @param category 数据分类ID，可以为空，可以为多个，多个使用逗号分隔
     * @param channel 通道ID，可以为空，可以为多个，多个使用逗号分隔
     * @param stcd 站点编码，可以为空，可以为多个，多个使用逗号分隔
     * @return qc.module.qms.dto.exchange.config.ExchangeConfigQueryResultDto
     * @author QuCheng Tech
     * @since 2024/1/24
     */
    @RequestMapping(value = "/condition1", method = {RequestMethod.POST, RequestMethod.GET})
    public ExchangeConfigQueryResultDto getQueryConditionDataByParam(@RequestParam(value = "channel", required = false) String channel,
                                                                     @RequestParam(value = "category", required = false) String category,
                                                                     @RequestParam(value = "stcd", required = false) String stcd) throws QCPromptException {

        return getExchangeConfigQueryResult(channel, category, stcd);
    }

    /***
     * 根据指定输入得到查询交换数据条件选择输入数据，按照通道-->数据分类-->站点的顺序和关系进行选择；
     * 指定站点编码时，自动根据站点编码获取数据分类信息，并根据分类信息获取通道信息；
     * 指定数据分类ID时，自动根据分类ID获取通道信息；
     * 指定通道ID时，自动根据通道ID获取通道下所有的数据分类和站点信息；
     * 均为指定时，获取交换通道中所有的通道，根据通道逐个获取通道下所有的数据分类和站点信息；
     *
     * @param channelIdString 通道ID，可以为空，可以为多个，多个使用逗号分隔
     * @param categoryIdString 数据分类ID，可以为空，可以为多个，多个使用逗号分隔
     * @param stationCodeString 站点编码，可以为空，可以为多个，多个使用逗号分隔
     * @return qc.module.qms.dto.exchange.config.ExchangeConfigQueryResultDto
     * @author QuCheng Tech
     * @since 2024/1/24
     */
    ExchangeConfigQueryResultDto getExchangeConfigQueryResult(String channelIdString, String categoryIdString, String stationCodeString) throws QCPromptException {
        //根据token得到用户所在的部门集合，用于后面根据用户部门进行过滤站点权限控制
        List<Integer> userDeptIds = null;
        //在platform中使用直接调用，在其他项目中使用微服务之间调用方式
        String token = request.getHeader(QCAuthConstant.TOKEN_HEADER_KEY);
        //此处不需要判断token是否为空、是否有效，在调用platform中已经进处理，直接传入
        List<DeptSimpleDto> depts = getUserValidDepts(token);
        if (depts != null && depts.size() > 0x0) {
            userDeptIds = depts.stream().map(p -> p.getId()).collect(Collectors.toList());
        }
        if (userDeptIds == null || userDeptIds.size() < 0x1)
            throw new QCPromptException("根据用户获取到的关联部门信息为空，用户未关联部门无法获取有访问权限的站点");

        //传入的数据分类ID、通道ID和站点编码均可以为空或多个，先对传入的字符串进行分隔处理
        String[] channelIds = StringSplitUtil.Split(channelIdString, true);
        String[] categoryIds = StringSplitUtil.Split(categoryIdString, true);
        String[] stcds = StringSplitUtil.Split(stationCodeString, true);
        //前端输入的值不管是否正确均以前端输入的为准，否则会出现无访问权限的数据返回到前端中
        //过滤得到有权限的站点编码集合，同时判断站点与用户部门关联关系，如果请求param中指定的stcd不为空同时判断
        List<String> allValidStcds = null;
        if (stcds != null && stcds.length > 0x0)
            allValidStcds = stationService.getValidStcdsByUserDeptIds(userDeptIds, Arrays.asList(stcds));
        else
            allValidStcds = stationService.getValidStcdsByUserDeptIds(userDeptIds, null);
        if (allValidStcds == null || allValidStcds.size() < 0x1)
            throw new QCPromptException("根据用户获取到的有访问权限的站点为空");

        //根据有权限的站点编码获取站点信息
        List<StationDto> allValidStations = null;
        if (allValidStcds != null && allValidStcds.size() > 0x0)
            allValidStations = stationService.getByFilterStationCodes(allValidStcds.stream().toArray(String[]::new));

        //根据不同的输入条件，先对通道、数据分类和站点信息进行查询，后面根据结果进行统一处理
        List<ExchangeChannelDto> resultChannels = new ArrayList<>();
        List<ExchangeCategoryDto> resultCategories = new ArrayList<>();
        List<StationDto> resultStations = new ArrayList<>();
        List<ExchangeCategoryStation> resultCategoryStations = new ArrayList<>();
        //判断处理从最小范围开始，按照站点-->数据分类-->通道的顺序进行处理
        if (stcds != null && stcds.length > 0x0) {
            //先查询站点信息，再根据站点编码查询所属的数据分类信息，再根据数据分类ID查询所属的通道信息
            if (allValidStations != null && allValidStations.size() > 0x0) {
                //查询的站点信息不为空，根据站点编码查询分类
                resultCategories = categoryService.getByStationCodes(allValidStations.stream().map(p -> p.getStcd()).toArray(String[]::new));

                if (resultCategories != null && resultCategories.size() > 0x0) {
                    //查询的分类信息不为空，根据分类信息查询通道
                    resultChannels = channelService.getByCategoryIds(resultCategories.stream().map(p -> p.getId()).toArray(Integer[]::new));
                    //根据数据分类查询关联站点信息
                    resultCategoryStations = categoryStationService.getCategoryStations(resultCategories.stream().map(p -> p.getId()).toArray(Integer[]::new));
                }
            }
        } else if (categoryIds != null && categoryIds.length > 0x0) {
            //先转换数据分类ID，查询数据分类信息，再根据数据分类ID查询所属的通道信息，查询分类中的站点信息
            //数据分类ID、通道ID必须能转换为int数值
            List<Integer> intCategoryIds = new ArrayList<>();
            for (String str : categoryIds) {
                try {
                    int intValue = Integer.parseInt(str);
                    intCategoryIds.add(intValue);
                } catch (NumberFormatException ex) {
                    //转换错误不进行处理
                }
            }
            resultCategories = categoryService.getByFilterIds(intCategoryIds.stream().toArray(Integer[]::new));
            if (resultCategories != null && resultCategories.size() > 0x0) {
                //查询的分类信息不为空，根据分类信息查询通道
                resultChannels = channelService.getByCategoryIds(resultCategories.stream().map(p -> p.getId()).toArray(Integer[]::new));

                //根据数据分类查询关联站点信息
                resultCategoryStations = categoryStationService.getCategoryStations(resultCategories.stream().map(p -> p.getId()).toArray(Integer[]::new));
            }
        } else if (channelIds != null && channelIds.length > 0x0) {
            //先转换通道ID，查询通道信息，查询通道中的数据分类信息，查询数据分类中的站点信息
            List<Integer> intChannelIds = new ArrayList<>();
            for (String str : channelIds) {
                try {
                    int intValue = Integer.parseInt(str);
                    intChannelIds.add(intValue);
                } catch (NumberFormatException ex) {
                    //转换错误不进行处理
                }
            }
            resultChannels = channelService.getByFilterIds(intChannelIds.stream().toArray(Integer[]::new));
            if (resultChannels != null && resultChannels.size() > 0x0) {
                //查询的通道信息不为空，根据通道信息查询分类
                resultCategories = categoryService.getByChannelIds(resultChannels.stream().map(p -> p.getId()).toArray(Integer[]::new));
                if (resultCategories != null && resultCategories.size() > 0x0) {
                    //根据数据分类查询关联站点信息
                    resultCategoryStations = categoryStationService.getCategoryStations(resultCategories.stream().map(p -> p.getId()).toArray(Integer[]::new));
                }
            }
        } else {
            //均没有传入，返回所有通道及所有分类和站点
            resultChannels = channelService.getAll();
            resultCategories = categoryService.getAll();
            resultStations = stationService.getAll();
            resultCategoryStations = categoryStationService.getAllCategoryStation();
        }

        //for (StationDto stationDto : allValidStations) {
        //    System.out.println("foreach allValidStations  stationDto.stcd：" + stationDto.getStcd() + " stationDto.stnm=" + stationDto.getStnm());
        //}

        //得到的分类关联站点信息不为空时，根据站点编码集合信息查询站点信息
        if (resultCategoryStations != null && resultCategoryStations.size() > 0x0) {
            //for (ExchangeCategoryStation categoryStation : resultCategoryStations) {
            //    System.out.println("foreach resultCategoryStations  categoryStation.getCid：" + categoryStation.getCid() + " categoryStation.getStcd=" + categoryStation.getStcd());
            //}
            //获取数据分类中的站点集合与有权限站点集合的交集，如果在数据分类的站点中但没有权限也不显示该站点
            List<String> categoryStcds = resultCategoryStations.stream().map(p -> p.getStcd()).collect(Collectors.toList());
            if (allValidStations != null && allValidStations.size() > 0x0) {
                for (StationDto stationDto : allValidStations) {
                    if (categoryStcds.contains(stationDto.getStcd()))
                        resultStations.add(stationDto);
                }
            }
        }

        //判断根据不同输入得到的信息有效，需要从通道信息开始
        if (resultChannels != null && resultChannels.size() > 0x0) {
            ExchangeConfigQueryResultDto result = new ExchangeConfigQueryResultDto();
            List<ExchangeChannelItemDto> channelItems = new ArrayList<>();
            List<DBTableSqlSimpleDto> resultSqls = new ArrayList<>();

            //遍历交换通道
            for (ExchangeChannelDto channel : resultChannels) {
                ExchangeChannelItemDto channelItemDto = new ExchangeChannelItemDto();
                channelItemDto.setChannel(channel);
                //通道中的分类信息
                if (resultCategories != null && resultCategories.size() > 0x0) {
                    //根据当前通道ID筛选分类
                    List<ExchangeCategoryDto> channelCategories = resultCategories.stream().filter(p -> p.getCid() == channel.getId()).collect(Collectors.toList());
                    if (channelCategories != null && channelCategories.size() > 0x0) {
                        List<ExchangeCategoryItemDto> channelCategoryItems = new ArrayList<>();
                        //遍历当前通道中的分类
                        for (ExchangeCategoryDto channelCategory : channelCategories) {
                            ExchangeCategoryItemDto categoryItem = new ExchangeCategoryItemDto();
                            categoryItem.setCategory(channelCategory);
                            //根据数据分类对应的表名获取sql信息
                            System.out.println("channelCategory.getId()=" + channelCategory.getId() + " channelCategory.getTablecode()=" + channelCategory.getTablecode());
                            List<DBTableSqlSimpleDto> categorySqls = getDbTableSqlSimples(channelCategory.getTablecode());
                            if (categorySqls != null && categorySqls.size() > 0x0) {
                                categoryItem.setSqls(categorySqls);
                                //处理返回结果中的sql取并集，如果已经存在相同的sql语句代码不进行添加
                                List<String> existSqls = resultSqls.stream().map(p -> p.getCode()).collect(Collectors.toList());
                                for (DBTableSqlSimpleDto sql : categorySqls) {
                                    if (existSqls.size() < 0x1 || !existSqls.contains(sql.getCode())) {
                                        resultSqls.add(sql);
                                    }
                                }
                            }
                            //获取数据分类中的站点集合
                            if (resultStations != null && resultStations.size() > 0x0) {
                                //for (StationDto stationDto : resultStations) {
                                //    System.out.println("根据分类ID获取站点信息，for resultStations  stationDto.stcd：" + stationDto.getStcd() + " stationDto.stnm=" + stationDto.getStnm());
                                //}
                                //根据当前的分类ID筛选站点，由于使用分类与站点关联表，需要先根据分类ID筛选处站点编码集合
                                if (resultCategoryStations != null && resultCategoryStations.size() > 0x0) {
                                    //根据分类ID筛选站点编码
                                    List<String> categoryStcds = resultCategoryStations.stream().filter(p -> p.getCid() == channelCategory.getId()).map(p -> p.getStcd()).collect(Collectors.toList());
                                    if (categoryStcds != null && categoryStcds.size() > 0x0) {
                                        //根据站点编码筛选站点
                                        List<StationDto> filterCategoryStations = resultStations.stream().filter(p -> categoryStcds.contains(p.getStcd())).collect(Collectors.toList());
                                        if (filterCategoryStations != null && filterCategoryStations.size() > 0x0) {
                                            System.out.println("根据分类ID获取站点信息，分类ID：" + channelCategory.getId() + "得到的站点数量为：" + categoryStcds.size() + " stations数量为：" + resultStations.size() + " filterCategoryStations数量为： " + filterCategoryStations.size());
                                            categoryItem.setStations(filterCategoryStations);
                                        }
                                    }
                                }
                            }
                            channelCategoryItems.add(categoryItem);
                        }
                        channelItemDto.setCategories(channelCategoryItems);
                    }
                }

                channelItems.add(channelItemDto);
            }

            result.setChannels(channelItems);
            result.setSqls(resultSqls);
            return result;
        }

        return null;
    }

    /***
     * 查询交换数据--返回QcTable结果类型
     *
     * @param condition 查询条件
     * @return qc.module.qms.dto.exchangeData.ExchangeDataQueryResultDto
     * @author QuCheng Tech
     * @since 2024/1/28
     */
    @RequestMapping(value = "/query", method = {RequestMethod.POST, RequestMethod.GET})
    public ExchangeDataQueryQcTableResultDto queryQcTableResult(@RequestBody ExchangeDataQueryConditionDto condition) throws QCPromptException, SQLException {
        //判断输入条件是否正确，有些条件必须指定
        if (condition == null)
            throw new QCPromptException("查询数据指定的条件不能为空");
        if (condition.getCid() < 0x1)
            throw new QCPromptException("查询数据指定的数据分类ID错误");
        if (StringUtils.isBlank(condition.getSql()))
            throw new QCPromptException("查询数据指定的查询语句代码不能为空");
        if (StringUtils.isBlank(condition.getBtm()))
            throw new QCPromptException("查询数据指定的起始时间不能为空");
        if (StringUtils.isBlank(condition.getEtm()))
            throw new QCPromptException("查询数据指定的截止时间不能为空");
        //转换时间
        Date beginTime = DateUtil.parseDate(condition.getBtm());
        Date endTime = DateUtil.parseDate(condition.getEtm());
        if (beginTime == null)
            throw new QCPromptException("查询数据指定的起始时间错误，不是有效的时间");
        if (endTime == null)
            throw new QCPromptException("查询数据指定的截止时间错误，不是有效的时间");
        if (beginTime.after(endTime))
            throw new QCPromptException("查询数据指定的时间错误，起始时间不能大于截止时间");
        //转换站点编码
        String[] stcds = StringSplitUtil.Split(condition.getStcd(), true);

        //需要先根据交换数据分类ID获取对应的数据库表，再根据数据库表查询出表上配置的SQL语句，根据传入的sql语句编码确定本次使用的sql语句
        String tableCode = categoryService.getTableCode(condition.getCid());
        if (StringUtils.isBlank(tableCode))
            throw new QCPromptException("根据数据分类ID得到的数据库表代码为空，指定的数据分类ID错误或系统配置错误");
        //根据数据库表和查询语句代码得到数据库表信息（包含列信息、sql语句等信息）
        //根据数据库表编码获取完整信息，再从信息中获取sql语句、查询数据库连接信息和列信息
        DBTableFullDto tableFullInfo = getDbTableFullInfo(tableCode);
        String sql = "";// getDbTableSqlString(tableCode, condition.getSql());
        //数据库连接信息
        String dbUrl = "";
        String dbUser = "";
        String dbPassword = "";
        if (tableFullInfo != null) {
            List<DBTableSqlDto> tableSqls = tableFullInfo.getSqls();
            if (tableSqls != null && tableSqls.size() > 0x0) {
                for (DBTableSqlDto sqlDto : tableSqls) {
                    if (condition.getSql().equalsIgnoreCase(sqlDto.getCode())) {
                        sql = sqlDto.getSqlstr();
                        break;
                    }
                }
            }
            if (tableFullInfo.getDb() != null) {
                //解析数据库连接信息
                String dbConJsonString = tableFullInfo.getDb().getCon();
                if (StringUtils.isNotBlank(dbConJsonString)) {
                    //Json对象进行获取
                    dbUrl = JsonParserUtil.getString(dbConJsonString, "url");
                    dbUser = JsonParserUtil.getString(dbConJsonString, "user");
                    dbPassword = JsonParserUtil.getString(dbConJsonString, "password");
                }
            }
        }

        List<Map<String, Object>> queryResult = exchangeDataService.query(dbUrl, dbUser, dbPassword, sql, stcds, beginTime, endTime);
        //查询结果中List中的项为数据库表数据行，每行中的每列数据在Map中，Map中key为列名，value为列的数据值
        //处理查询结果，按前端返回数据结果进行转换
        ExchangeDataQueryQcTableResultDto result = new ExchangeDataQueryQcTableResultDto();
        //如果查询结果中没有任何行，也不需要返回查询列信息和站点信息
        if (queryResult != null && queryResult.size() > 0x0) {
            QCTable datatable = new QCTable();
            //使用第1行数据解析列名
            List<QCTableCell> headerRowCells = new ArrayList<>();
            Iterator<Map.Entry<String, Object>> firstRowIterator = queryResult.get(0x0).entrySet().iterator();
            while (firstRowIterator.hasNext()) {
                Map.Entry<String, Object> entry = firstRowIterator.next();
                //entry中的顺序与sql语句中指定的顺序不一致，原因未知
                System.out.println("entry.key:" + entry.getKey() + " entry.value:" + entry.getValue().toString());
                QCTableCell cell = new QCTableCell();
                //固定都是占1行1列
                cell.setRowspan(0x1);
                cell.setColspan(0x1);
                cell.setText(entry.getKey());

                headerRowCells.add(cell);
            }
            //默认都只有单行表头
            QCTableRow headerRow = new QCTableRow();
            headerRow.setCells(headerRowCells);
            List<QCTableRow> headerRows = new ArrayList<>();
            headerRows.add(headerRow);
            datatable.setHeaderRows(headerRows);

            //遍历行
            List<QCTableRow> bodyRows = new ArrayList<>();
            for (Map<String, Object> datarow : queryResult) {
                List<QCTableCell> bodyRowCells = new ArrayList<>();
                //System.out.println("listItem");
                Iterator<Map.Entry<String, Object>> iterator = datarow.entrySet().iterator();
                while (iterator.hasNext()) {
                    Map.Entry<String, Object> entry = iterator.next();
                    //System.out.println("entry.key:" + entry.getKey() + " entry.value:" + entry.getValue().toString());
                    QCTableCell cell = new QCTableCell();
                    //固定都是占1行1列
                    cell.setRowspan(0x1);
                    cell.setColspan(0x1);
                    cell.setText(entry.getValue().toString());

                    bodyRowCells.add(cell);
                }
                QCTableRow bodyRow = new QCTableRow();
                bodyRow.setCells(bodyRowCells);
                bodyRows.add(bodyRow);
            }
            datatable.setBodyRows(bodyRows);

            result.setData(datatable);
        }

        return result;
    }


    /***
     * 查询交换数据--返回原始结果类型，List<Map<String, Object>>
     *
     * @param condition 查询条件
     * @return qc.module.qms.dto.exchangeData.ExchangeDataQueryResultDto
     * @author QuCheng Tech
     * @since 2024/1/28
     */
    @RequestMapping(value = "/queryraw", method = {RequestMethod.POST, RequestMethod.GET})
    public ExchangeDataQueryRawDataResultDto queryRawDataResult(@RequestBody ExchangeDataQueryConditionDto condition) throws QCPromptException, SQLException, ParseException {
        long startTime = System.currentTimeMillis();
        System.out.println("=======开始处理时间===========" + startTime);
        //判断输入条件是否正确，有些条件必须指定
        if (condition == null)
            throw new QCPromptException("查询数据指定的条件不能为空");
        if (condition.getCid() < 0x1)
            throw new QCPromptException("查询数据指定的数据分类ID错误");
        if (StringUtils.isBlank(condition.getSql()))
            throw new QCPromptException("查询数据指定的查询语句代码不能为空");
        if (StringUtils.isBlank(condition.getBtm()))
            throw new QCPromptException("查询数据指定的起始时间不能为空");
        if (StringUtils.isBlank(condition.getEtm()))
            throw new QCPromptException("查询数据指定的截止时间不能为空");
        if (condition.getIsOnlyStatics() == null)
            throw new QCPromptException("是否只统计不能为空");
        //转换时间
        Date beginTime = DateUtil.parseDate(condition.getBtm());
        Date endTime = DateUtil.parseDate(condition.getEtm());
        if (beginTime == null)
            throw new QCPromptException("查询数据指定的起始时间错误，不是有效的时间");
        if (endTime == null)
            throw new QCPromptException("查询数据指定的截止时间错误，不是有效的时间");
        if (beginTime.after(endTime))
            throw new QCPromptException("查询数据指定的时间错误，起始时间不能大于截止时间");
        //转换站点编码
        String[] stcds = StringSplitUtil.Split(condition.getStcd(), true);

        //需要先根据交换数据分类ID获取对应的数据库表，再根据数据库表查询出表上配置的SQL语句，根据传入的sql语句编码确定本次使用的sql语句
        String tableCode = categoryService.getTableCode(condition.getCid());
        if (StringUtils.isBlank(tableCode))
            throw new QCPromptException("根据数据分类ID得到的数据库表代码为空，指定的数据分类ID错误或系统配置错误");
        //根据数据库表编码获取完整信息，再从信息中获取sql语句、查询数据库连接信息和列信息
        DBTableFullDto tableFullInfo = getDbTableFullInfo(tableCode);
        String sql = "";// getDbTableSqlString(tableCode, condition.getSql());
        //数据库连接信息
        String dbUrl = "";
        String dbUser = "";
        String dbPassword = "";
        if (tableFullInfo != null) {
            List<DBTableSqlDto> tableSqls = tableFullInfo.getSqls();
            if (tableSqls != null && tableSqls.size() > 0x0) {
                for (DBTableSqlDto sqlDto : tableSqls) {
                    if (condition.getSql().equalsIgnoreCase(sqlDto.getCode())) {
                        sql = sqlDto.getSqlstr();
                        break;
                    }
                }
            }
            if (tableFullInfo.getDb() != null) {
                //解析数据库连接信息
                String dbConJsonString = tableFullInfo.getDb().getCon();
                if (StringUtils.isNotBlank(dbConJsonString)) {
                    //Json对象进行获取
                    dbUrl = JsonParserUtil.getString(dbConJsonString, "url");
                    dbUser = JsonParserUtil.getString(dbConJsonString, "user");
                    dbPassword = JsonParserUtil.getString(dbConJsonString, "password");
                }
            }
        }


        List<Map<String, Object>> queryResult = exchangeDataService.query(dbUrl, dbUser, dbPassword, sql, stcds, beginTime, endTime);
        long finishTime = System.currentTimeMillis();
        System.out.println("=======结束处理条件并查询数据库时间===========" + finishTime);
        System.out.println("=====处理条件并查询数据库消耗时间==========" + (finishTime - startTime) + "ms");
        System.out.println("=======开始处理表格数据时间===========" + finishTime);
        ExchangeDataQueryRawDataResultDto result = new ExchangeDataQueryRawDataResultDto();
        result.setData(queryResult);
        //设置返回的站点信息
        //根据分类ID筛选站点编码，先根据交换数据分类ID获取所有站点，如果传入的站点编码不为空根据传入的进行筛选
        List<StationDto> stations = null;
        if (stcds != null && stcds.length > 0x0)
            stations = stationService.getByFilterStationCodes(stcds);
        else
            stations = stationService.getByCategoryId(condition.getCid());
        result.setStations(stations);
        //设置返回的列信息，不管是否有查询结果数据均需要返回列信息
        System.out.println("getQueryFieldNamesFromSql sql=" + sql);
        LinkedHashMap<String, String> queryFields = (queryResult != null && queryResult.size() > 0x0) ? getQueryFieldNamesFromResultData(queryResult.get(0x0)) : getQueryFieldNamesFromSql(sql);
        //LinkedHashMap<String, String> queryFields = getQueryFieldNamesFromSql(sql);
        if (queryFields != null && queryFields.size() > 0x0) {
            //获取数据库表的列信息进行逐个处理
            //使用数据库表列信息遍历可能会出现查询结果列不在定义的列中导致无法处理，直接使用查询结果列又会出现列的先后顺序不是逾期
            //在此单独构造出一个返回结果列集合，先按照数据库表列顺序（要求查询结果中包含当前列），再加入查询结果返回列（数据库表列定义中没有的）
            List<String> queryResultFieldInColumns = new ArrayList<>();
            List<String> queryResultFieldNotInColumns = new ArrayList<>();
            List<DBTableColumnDto> tableColumns = tableFullInfo.getColumns();
            if (tableColumns != null && tableColumns.size() > 0x0) {
                //有定义数据库表列信息，根据查询结果列判断是否需要在返回列信息中
                for (String queryResultFieldName : queryFields.keySet()) {
                    //判断查询结果列是否在数据库表列定义中
                    Optional<DBTableColumnDto> selectedColumn = tableColumns.stream().filter(p -> p.getCode().equalsIgnoreCase(queryResultFieldName)).findFirst();
                    if (selectedColumn != null && selectedColumn.isPresent()) {
                        queryResultFieldInColumns.add(queryResultFieldName);
                    } else {
                        queryResultFieldNotInColumns.add(queryResultFieldName);
                    }
                }
            } else {
                //如果没有定义数据库表列信息，全部使用查询结果中的返回列
                queryResultFieldNotInColumns = queryFields.keySet().stream().collect(Collectors.toList());
            }

            //按照查询sql语句中的字段顺序进行返回，如果在列集合中没有对应的信息使用初始值
            List<DBTableColumnDto> columns = new ArrayList<>();
            //列集合结果的顺序按照数据库表列配置的顺序；先从数据库表列配置的集合中遍历添加，再从没有对应数据库表列配置的集合中遍历添加
            if (queryResultFieldInColumns != null && queryResultFieldInColumns.size() > 0x0) {
                for (DBTableColumnDto tableColumn : tableColumns) {
                    //遍历数据库表列集合，保证顺序是按照固定的顺序返回
                    Optional<String> selectedColumn = queryResultFieldInColumns.stream().filter(p -> p.equalsIgnoreCase(tableColumn.getCode())).findFirst();
                    if (selectedColumn != null && selectedColumn.isPresent()) {
                        //如果列在查询返回结果中存在，直接使用数据库表列信息
                        columns.add(tableColumn);
                    }
                }
            }
            //遍历不在数据库表列中的列名进行添加
            if (queryResultFieldNotInColumns != null && queryResultFieldNotInColumns.size() > 0x0) {
                for (String fieldName : queryResultFieldNotInColumns) {
                    DBTableColumnDto column = new DBTableColumnDto();
                    column.setCode(fieldName);
                    //没有对应的列信息，使用默认值
                    column.setDatatype(DataTypeEnum.STRING.getIndex());
                    column.setName(fieldName);
                    //列的属性中默认值设置，否则在界面中无法显示
                    column.setQrshow(true);
                    column.setEfshow(true);

                    columns.add(column);
                }
            }
            result.setColumns(columns);
        }

        long endhandleDataTime = System.currentTimeMillis();
        System.out.println("=========结束处理表格数据时间===========" + endhandleDataTime);
        System.out.println("============处理表格数据消耗时间==========" + (endhandleDataTime - finishTime) + "ms");
        System.out.println("==========数据统计处理开始时间==========" + endhandleDataTime);

        /* 处理交换数据统计结果 */
        if (Objects.nonNull(condition.getSts())) {
            ExchangeDataStatisticsResultDto sts = statistics(beginTime, endTime, result.getData(), result.getColumns(), condition.getSts());
            if (Objects.nonNull(sts)) result.setSts(sts);
        }

        //如果只统计，则只返回统计结果
        if (condition.getIsOnlyStatics()) {
            result.setData(null);
            result.setColumns(null);
            result.setStations(null);
        }

        long stsEndTime = System.currentTimeMillis();
        System.out.println("=========数据统计处理结束时间===========" + stsEndTime);
        System.out.println("============处理数据统计消耗时间==========" + (stsEndTime - endhandleDataTime) + "ms");
        System.out.println("=========处理消耗时间====" + (stsEndTime - startTime) + "ms");
        return result;
    }

    /**
     * 交换数据统计
     *
     * @param beginTime 开始时间条件
     * @param endTime   结束时间条件
     * @param data      数据集合
     * @param columns   列集合
     * @param condition 交换数据统计条件
     * @return ExchangeDataStatisticsResultDto 交换数据统计结果
     * @author QuCheng Tech
     * @since 2024/4/12
     */
    public ExchangeDataStatisticsResultDto statistics(Date beginTime,
                                                      Date endTime,
                                                      List<Map<String, Object>> data,
                                                      List<DBTableColumnDto> columns,
                                                      DataRecordStatisticsConditionDto condition) throws ParseException {

        long ratestsBeginTime = System.currentTimeMillis();
        System.out.println("===到报准点统计开始时间====" + ratestsBeginTime);
        //如果结束时间 > 当前时间，结束时间使用当前时间
        Date currentDate = new Date();
        if (endTime.after(currentDate)) {
            endTime = currentDate;
        }

        //交换数据统计结果
        ExchangeDataStatisticsResultDto result = new ExchangeDataStatisticsResultDto();

        /* 数据记录统计结果 */
        DataRecordStatisticsResultDto recordStatisticsResult = new DataRecordStatisticsResultDto();
        /* 数据记录统计结果参数值初始化 */
        //设置到报率统计，默认为false
        boolean ratests = false;
        //设置准点率统计，默认为false
        boolean ontimests = false;
        //应有数据条数
        int duecount = 0;
        //实有数据条数
        int realcount = 0;
        //缺失数据条数
        int lostcount = 0;
        //缺失数据时间集合
        List<String> losttms = new ArrayList<>();
        //准点到报数据条数
        int ontimecount = 0;
        //误点到报数据条数
        int mistimecount = 0;
        //误点数据时间集合
        List<Map<String, String>> mistms = new ArrayList<>();

        String tmCode = ""; //数据时标列编码
        String inTmCode = ""; //写入时间列编码

        //需要有数据频率才能统计到报和准点，数据频率必须同时有频率类型和频率数值
        if (Objects.nonNull(condition) && condition.getRatetype() > 0x0 && condition.getRatevalue() > 0x0) {
            //循环columns，找出数据记录统计结果需要的列编码
            for (DBTableColumnDto columnDto : columns) {
                //找到列类型为32-数据时标列对象，获取数据时标列编码
                if (columnDto.getColtype() == ColumnTypeEnum.TIME_STAMP.getIndex()) {
                    tmCode = columnDto.getCode();
                }
                //如果统计准点率，判断有没有写入时间列，没有就不统计
                if (condition.getOntime()) {
                    if (columnDto.getColtype() == ColumnTypeEnum.TIME_IN.getIndex()) {
                        inTmCode = columnDto.getCode();
                    }
                }
            }

            //必须要有数据时标列才能统计到报和准点
            //必须要有数据写入时间列才能统计准点
            if (StringUtils.isNotBlank(tmCode)) {
                ratests = true;
                if (condition.getOntime() && StringUtils.isNotBlank(inTmCode))
                    ontimests = true;
            }
        }

        //如果到报和准点统计至少有1个，进行统计处理
        //在处理前对查询出来的数据记录data中的时间提前进行转换处理，解决每次循环时转换出现的创建SimpleDateFormat对象耗时长的问题
        if (ratests == true || ontimests == true) {
            //从data中将数据时标列写入时间找出来存放在对应的map中，key保存行号，value为数据时标、写入时间，根据行号对应。
            Map<Integer, Date> tmMap = new HashMap<>();
            Map<Integer, Date> inTmMap = new HashMap<>();
            if (CollectionUtils.isNotEmpty(data)) {
                for (int i = 0; i < data.size(); i++) {
                    Date tm = DateUtil.parseDate(data.get(i).get(tmCode));
                    Date inTm = DateUtil.parseDate(data.get(i).get(inTmCode));
                    tmMap.put(i, tm);
                    inTmMap.put(i, inTm);
                }
            }

            //缺数时间格式化，可以根据传入的数据时间频率类型来
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

            //统计到报和准点使用的前后允许分钟数，默认为0，如果输入的有值使用输入的值
            Integer rateBeforeMinutes = 0x0;
            Integer rateAfterMinutes = 0x0;
            Integer ontimeBeforeMinutes = 0x0;
            Integer ontimeAfterMinutes = 0x0;
            if (condition.getRateafter() != null)
                rateAfterMinutes = Math.abs(condition.getRateafter());
            if (condition.getRatebefore() != null)
                rateBeforeMinutes = Math.abs(condition.getRatebefore());
            if (condition.getOntimebefore() != null)
                ontimeBeforeMinutes = Math.abs(condition.getOntimebefore());
            if (condition.getOntimeafter() != null)
                ontimeAfterMinutes = Math.abs(condition.getOntimeafter());

            //循环时间范围，处理统计条件数据
            while (beginTime.before(endTime)) {
                //根据频率允许提前分钟数、频率允许延后分钟数计算到报点有效范围
                Date rateBeginTm = DateUtil.addMinutes(beginTime, -rateBeforeMinutes);//减去ratebefore分钟
                Date rateEndTm = DateUtil.addMinutes(beginTime, rateAfterMinutes);//添加rateafter分钟

                //到报率统计
                if (ratests) {
                    //应有数据条数 + 1
                    duecount += 1;
                    //统计到爆率相关数据：查找数据中当前时间点到报有效范围内的数据记录，有数据则实有数据条数+1，没有则缺失数据条数+1并记录缺失时间
                    if (!tmMap.isEmpty()) {
                        List<Date> realData = new ArrayList<>();
                        tmMap.forEach((key, value) -> {
                            if (value != null && (value.equals(rateBeginTm) || value.equals(rateEndTm) ||
                                    value.after(rateBeginTm) && value.before(rateEndTm))) {
                                realData.add(value);
                            }
                        });

                        if (CollectionUtils.isNotEmpty(realData)) {
                            realcount += 1;
                        } else {
                            lostcount += 1;
                            losttms.add(sdf.format(beginTime));
                        }
                    } else {
                        lostcount += 1;
                    }
                }

                //统计准点率相关数据：查找数据中当前时间点准点有效范围内的数据记录，有数据则准点到报数据条数+1，否则误点到报数据条数+1并记录误点时间
                //如果统计准点率(有写入时间列)，但是统计数据中没有写入时间列则不统计准点率相关数据
                if (ontimests) {
                    if (!inTmMap.isEmpty()) {
                        //如果统计准点率
                        Date ontimeBeginTm = DateUtil.addMinutes(beginTime, -ontimeBeforeMinutes);
                        Date ontimeEndTm = DateUtil.addMinutes(beginTime, ontimeAfterMinutes);

                        List<Date> ontimeData = new ArrayList<>();
                        inTmMap.forEach((key, value) -> {
                            if (value != null && (value.equals(ontimeBeginTm) || value.equals(ontimeEndTm) ||
                                    value.after(ontimeBeginTm) && value.before(ontimeEndTm))) {
                                ontimeData.add(value);
                            }
                        });

                        if (CollectionUtils.isNotEmpty(ontimeData)) {
                            ontimecount += 1;
                        } else {
                            //误点
                            mistimecount += 1;
                            Map<String, String> mistmMap = new HashMap<>();
                            //根据当前误点时间获取对应的写入时间
                            Date currentInTm = null; //当前写入时间
                            //从tmMap中找出误点时间的行号，再根据行号到inTmMap中找出对应行号的写入时间
                            Integer row = null;
                            for (Integer key : tmMap.keySet()) {
                                if (beginTime.equals(tmMap.get(key))) {
                                    row = key;
                                }
                            }
                            if (row != null) {
                                for (Integer key : inTmMap.keySet()) {
                                    if (row.equals(key)) {
                                        currentInTm = inTmMap.get(key);
                                        System.out.println("========误点时间：" + beginTime + ",写入时间：" + currentInTm);
                                    }
                                }
                            }

                            if (currentInTm != null) {
                                mistmMap.put(sdf.format(beginTime), sdf.format(currentInTm));
                            } else {
                                mistmMap.put(sdf.format(beginTime), "");
                            }
                            mistms.add(mistmMap);
                        }
                    } else {
                        mistimecount += 1;
                    }
                }

                //根据不同时间频率类型和频率数值，计算下一个开始时间
                if (condition.getRatetype() == TimeRangeLengthEnum.YEAR.getIndex()) {
                    beginTime = DateUtil.addYears(beginTime, condition.getRatevalue());
                } else if (condition.getRatetype() == TimeRangeLengthEnum.QUARTER.getIndex()) {
                    //季度，月份+3
                    beginTime = DateUtil.addMonths(beginTime, condition.getRatevalue() * 0x3);
                } else if (condition.getRatetype() == TimeRangeLengthEnum.MONTH.getIndex()) {
                    beginTime = DateUtil.addMonths(beginTime, condition.getRatevalue());
                } else if (condition.getRatetype() == TimeRangeLengthEnum.TEN_DAYS.getIndex()) {
                    beginTime = DateUtil.addTendays(beginTime, condition.getRatevalue());
                } else if (condition.getRatetype() == TimeRangeLengthEnum.WEEK.getIndex()) {
                    //周，天数+7
                    beginTime = DateUtil.addDays(beginTime, condition.getRatevalue() * 0x7);
                } else if (condition.getRatetype() == TimeRangeLengthEnum.DAY.getIndex()) {
                    beginTime = DateUtil.addDays(beginTime, condition.getRatevalue());
                } else if (condition.getRatetype() == TimeRangeLengthEnum.HOUR.getIndex()) {
                    beginTime = DateUtil.addHours(beginTime, condition.getRatevalue());
                } else if (condition.getRatetype() == TimeRangeLengthEnum.MINUTE.getIndex()) {
                    beginTime = DateUtil.addMinutes(beginTime, condition.getRatevalue());
                } else {
                    break;
                }
            }

            //优化减少返回数据，如果数据全部缺失或全部误点，缺失和误点的时间集合设置为空
            if (duecount == lostcount)
                losttms = new ArrayList<>();
            if (duecount == mistimecount)
                mistms = new ArrayList<>();
        }

        /* 数据记录统计结果 */
        recordStatisticsResult.setRatests(ratests);
        recordStatisticsResult.setOntimests(ontimests);
        recordStatisticsResult.setDuecount(duecount);
        recordStatisticsResult.setRealcount(realcount);
        recordStatisticsResult.setLostcount(lostcount);
        recordStatisticsResult.setLosttms(losttms);
        recordStatisticsResult.setOntimecount(ontimecount);
        recordStatisticsResult.setMistimecount(mistimecount);
        recordStatisticsResult.setMistms(mistms);

        result.setRecordsts(recordStatisticsResult);

        long ratestsEndTime = System.currentTimeMillis();
        System.out.println("===到报准点统计结束时间====" + ratestsEndTime);
        System.out.println("到报准点统计消耗时间===" + (ratestsEndTime - ratestsBeginTime) + "ms");

        /* 数据值统计 */
        List<DataValueStatisticsResultDto> valuests = valuests(data, columns, tmCode);
        result.setValuests(valuests);

        /* 状态统计 */
        List<ExchangeDataStatustsDto> statusts = statusts(data, columns);
        result.setStatusts(statusts);

        return result;
    }

    /**
     * 数据值统计
     *
     * @param data    数据集合
     * @param columns 列集合
     * @param tmCode  时间列编码
     * @return List<DataValueStatisticsResultDto> 数据值统计结果DTO集合
     * @author QuCheng Tech
     * @since 2024/4/15
     */
    private List<DataValueStatisticsResultDto> valuests(List<Map<String, Object>> data, List<DBTableColumnDto> columns, String tmCode) {
        long ratestsBeginTime = System.currentTimeMillis();
        System.out.println("===数据值统计开始时间====" + ratestsBeginTime);
        /* 数据值统计 */
        List<DataValueStatisticsResultDto> valuests = new ArrayList<>();
        if (CollectionUtils.isNotEmpty(data)) {
            if (CollectionUtils.isNotEmpty(columns)) {
                if (StringUtils.isNotBlank(tmCode)) {
                    for (DBTableColumnDto columnDto : columns) {
                        //如果列类型为40-数据值列，则统计相关数据
                        if (columnDto.getColtype() == ColumnTypeEnum.VALUE.getIndex()) {
                            DataValueStatisticsResultDto dataValueStsDto = new DataValueStatisticsResultDto();
                            dataValueStsDto.setName(columnDto.getName());
                            dataValueStsDto.setUnit(columnDto.getUnit());
                            int count = 0;//数据条数
                            double sumv = 0;//合计值
                            //最大值、最大值时间、最小值、最小值时间默认设置为第一条数据的值
                            String value0 = "0";
                            if (data.get(0).get(columnDto.getCode()) != null){//判空避免空指针
                                value0 = data.get(0).get(columnDto.getCode()).toString();
                            }
                            double maxv = Double.parseDouble(value0);
                            double minx = Double.parseDouble(value0);
                            String maxtm = "";
                            String mintm = "";
                            if (data.get(0).get(tmCode) != null){
                                maxtm = data.get(0).get(tmCode).toString();
                                mintm = data.get(0).get(tmCode).toString();
                            }
                            for (int i = 0; i < data.size(); i++) {
                                count += 1;
                                String value = "0";
                                if (data.get(i).get(columnDto.getCode()) != null){//判空避免空指针
                                    value = data.get(i).get(columnDto.getCode()).toString();
                                }
                                sumv += Double.parseDouble(value);
                                if (maxv <= Double.parseDouble(value)) {
                                    maxv = Double.parseDouble(value);
                                    maxtm = data.get(i).get(tmCode).toString();
                                }
                                if (minx >= Double.parseDouble(value)) {
                                    minx = Double.parseDouble(value);
                                    mintm = data.get(i).get(tmCode).toString();
                                }
                            }
                            //获取列的格式化字符串
                            String formarStr = "";
                            if (StringUtils.isNotBlank(columnDto.getFormatstr())){
                                formarStr = columnDto.getFormatstr();   
                            }
                            //平均值
                            double avgv = sumv / count;
                            //差值
                            double diffv = maxv - minx;
                            dataValueStsDto.setSumv(NumberUtil.parseNumberToString(sumv,formarStr));
                            dataValueStsDto.setAvgv(NumberUtil.parseNumberToString(avgv,formarStr));
                            dataValueStsDto.setMaxv(NumberUtil.parseNumberToString(maxv,formarStr));
                            dataValueStsDto.setMaxtm(maxtm);
                            dataValueStsDto.setMinv( NumberUtil.parseNumberToString(minx,formarStr));
                            dataValueStsDto.setMintm(mintm);
                            dataValueStsDto.setDiffv(NumberUtil.parseNumberToString(diffv,formarStr));
                            valuests.add(dataValueStsDto);
                        }
                    }
                }
            }
        }
        long ratestsEndTime = System.currentTimeMillis();
        System.out.println("===数据值统计结束时间====" + ratestsEndTime);
        System.out.println("======数据值统计消耗时间===" + (ratestsEndTime - ratestsBeginTime) + "ms");
        return valuests;
    }

    /**
     * 状态统计
     *
     * @param data    数据集合
     * @param columns 列集合
     * @return List<KeyValuePairDto> 状态统计集合
     * @author QuCheng Tech
     * @since 2024/4/15
     */
    private List<ExchangeDataStatustsDto> statusts(List<Map<String, Object>> data, List<DBTableColumnDto> columns) {
        //返回结果集
        List<ExchangeDataStatustsDto> statusts = new ArrayList<>();

        long ratestsBeginTime = System.currentTimeMillis();
        System.out.println("===状态统计开始时间====" + ratestsBeginTime);
        if (CollectionUtils.isNotEmpty(data)) {
            for (DBTableColumnDto columnDto : columns) {

                if (columnDto.getDatatype() != DataTypeEnum.INT.getIndex() && columnDto.getDatatype() != DataTypeEnum.BOOL.getIndex()) {
                    continue;
                }

                //在列类型为：70-字典、71-枚举字典、72-数据字典时均进行处理；
                if (columnDto.getColtype() == ColumnTypeEnum.DICT.getIndex() ||
                        columnDto.getColtype() == ColumnTypeEnum.DICT_ENUM.getIndex() ||
                        columnDto.getColtype() == ColumnTypeEnum.DICT_DEFINE.getIndex()) {
                    if (columnDto.getOptions() != null && columnDto.getOptions().size() > 0x0) {
                        ExchangeDataStatustsDto statustsDto = new ExchangeDataStatustsDto();
                        List<KeyValuePairDto> colOptions = new ArrayList<>();
                        //记录统计为0的状态数量
                        int isZeroCount = 0x0;
                        //如果列的数据类型为整数，进行数据类型转换处理
                        if (columnDto.getDatatype() == DataTypeEnum.INT.getIndex()) {
                            for (KeyValuePairDto keyValuePairDto : columnDto.getOptions()) {
                                int count = 0;
                                for (Map<String, Object> map : data) {
                                    if (Integer.parseInt(keyValuePairDto.getKey()) == Integer.parseInt(map.get(columnDto.getCode()).toString())) {
                                        count++;
                                    }
                                }
                                if (count == 0x0) isZeroCount ++;
                                KeyValuePairDto colOption = new KeyValuePairDto();
                                colOption.setKey(keyValuePairDto.getValue());
                                colOption.setValue(String.valueOf(count));
                                colOptions.add(colOption);
                            }
                            statustsDto.setKey(columnDto.getName());
                            //statustsDto.setValue(colOptions);
                            //一个状态列中将状态统计为0的合并显示为：其他（n），n表示统计为0的状态数量；如果n为1，就不需要合并显示；
                            if (isZeroCount > 0x1){
                                statustsDto.setValue(mergeOptions(colOptions,isZeroCount));
                            }else {
                                statustsDto.setValue(colOptions);
                            }
                            statusts.add(statustsDto);
                        }
                        //如果列的数据类型为布尔
                        else if (columnDto.getDatatype() == DataTypeEnum.BOOL.getIndex()) {
                            for (int i = 0; i < columnDto.getOptions().size(); i++) {
                                int count = 0;
                                //列的options的key字符串为true或者1时，认为是true，默认为false
                                boolean key = false;
                                //布尔类型，只取前两个option的key来处理，多余的option不管，默认第一个和第二个option就是布尔类型的两个情况
                                if (i < 0x2) {
                                    if (columnDto.getOptions().get(i).getKey().equals("true") ||
                                            columnDto.getOptions().get(i).getKey().equals("1")) {
                                        key = true;
                                    }
                                    for (Map<String, Object> map : data) {
                                        String obj = map.get(columnDto.getCode()).toString();
                                        //数据值默认为false
                                        boolean colvalue = false;
                                        //数据值为字符串的true或者1时，数据值认为是true，默认为false
                                        if (obj.equals("true") || obj.equals("1")) {
                                            colvalue = true;
                                        }
                                        if (key == colvalue) {
                                            count++;
                                        }
                                    }
                                    if (count == 0x0) isZeroCount ++;
                                    KeyValuePairDto colOption = new KeyValuePairDto();
                                    colOption.setKey(columnDto.getOptions().get(i).getValue());
                                    colOption.setValue(String.valueOf(count));
                                    colOptions.add(colOption);
                                }
                            }
                            statustsDto.setKey(columnDto.getName());
                            //statustsDto.setValue(colOptions);
                            //一个状态列中将状态统计结果为0的处理显示为"其它(n)"，n表示为0的状态数量，为1不合并显示
                            if (isZeroCount > 0x1){
                                statustsDto.setValue(mergeOptions(colOptions,isZeroCount));
                            }else {
                                statustsDto.setValue(colOptions);
                            }
                            statusts.add(statustsDto);
                        }

                    }
                }
            }
        }
        long ratestsEndTime = System.currentTimeMillis();
        System.out.println("===状态统计结束时间====" + ratestsEndTime);
        System.out.println("======状态统计消耗时间===" + (ratestsEndTime - ratestsBeginTime) + "ms");
        return statusts;
    }

    /**
     * 状态统计合并处理：
     * 一个状态列中将状态统计结果为0的处理显示为"其它(n)"，n表示为0的状态数量，为1不合并显示
     * 
     * @param sourceOptions 源数据 一个状态列的所有option
     * @param isZeroCount 状态统计结果为0的状态数量
     * @return List<KeyValuePairDto>
     * @author QuCheng Tech
     * @since 2024/5/10
     */
    private List<KeyValuePairDto> mergeOptions(List<KeyValuePairDto> sourceOptions,int isZeroCount){
        List<KeyValuePairDto> mergeOptions = new ArrayList<>();
        //找出状态数量不为0的
        for (KeyValuePairDto dto : sourceOptions){
            if (Integer.parseInt(dto.getValue()) != 0x0){
                //count不为0，不需要合并
                mergeOptions.add(dto);
            }
        }
        //状态数量为0的合并统计
        KeyValuePairDto mergeOption = new KeyValuePairDto();
        mergeOption.setKey("其它(" + isZeroCount + ")");
        mergeOption.setValue("0");
        mergeOptions.add(mergeOption);
        return mergeOptions;
    }

    @PostMapping(value = "/save")
    public String save(@RequestBody ExchangeDataSaveDto data) {
        //可能存在保存数据的属性需要从公用参数json中获取，因此需要传入或者查询出参数信息
        if (data == null)
            return "保存数据对象不能为空";
        if (data.getData() == null || data.getData().size() < 0x1)
            return "保存数据的数据值集合不能为空";
        //根据交换数据分类获取对应的数据库表编码
        ExchangeCategoryDto category = categoryService.get(data.getCid());
        if (category == null)
            return "根据指定的通道ID获取到的数据交换通道信息为空";
        if (StringUtils.isBlank(category.getTablecode()))
            return "根据指定的通道ID获取到的数据交换通道信息中数据库表为空，无法进行数据保存操作";

        DBTableDataOperateDto tableDataSaveDto = new DBTableDataOperateDto();
        tableDataSaveDto.setTablecode(category.getTablecode());
        tableDataSaveDto.setData(data.getData());
        tableDataSaveDto.setObjid(data.getObjid());
        tableDataSaveDto.setObjname(data.getObjname());
        tableDataSaveDto.setObjparam(data.getObjparam());
        //如果需要用户信息，根据token获取
        //调用平台的数据库表数据保存接口
        ServiceInstance service = discoveryClient.getInstances("module-platform").get(0);
        String url = "http://" + service.getHost() + ":" + service.getPort() + "/dbtabledata/save";

        HttpEntity<DBTableDataOperateDto> formEntity = new HttpEntity<DBTableDataOperateDto>(tableDataSaveDto, null);
        ParameterizedTypeReference<String> responseType = new ParameterizedTypeReference<String>() {
        };

        ResponseEntity<String> response = restTemplate.exchange(
                url,//获取资源的地址
                HttpMethod.POST,
                formEntity,
                responseType
        );
        return response.getBody();
    }

    /***
     * 从查询数据结果中获取列信息
     *
     * @param data 查询数据结果行
     * @return java.util.List<java.lang.String>
     * @author QuCheng Tech
     * @since 2024/1/30
     */
    LinkedHashMap<String, String> getQueryFieldNamesFromResultData(Map<String, Object> data) {
        if (data != null && data.size() > 0x0) {
            LinkedHashMap<String, String> result = new LinkedHashMap<>();

            for (String str : data.keySet()) {
                result.put(str, str);
            }

            return result;
        }

        return null;
    }

    /***
     * 从指定的sql语句中获取查询的字段集合(key为原始字段名称，value为别名)；如 stcd as code
     * stcd用于根据数据库表列信息获取，code用于最终对应数据行的显示
     *
     * @param sql sql语句
     * @return java.util.List<java.lang.String>
     * @author QuCheng Tech
     * @since 2024/1/30
     */
    LinkedHashMap<String, String> getQueryFieldNamesFromSql(String sql) {
        if (!StringUtils.isBlank(sql)) {
            //select xx1,xx2 as xx2,xxx as xx3 from ...
            //select CONVERT(varchar(100), INSERTTIME, 126) as INSERTTIME from
            //固定从select和from中间获取，中间的使用逗号分隔
            int fieldStringBeginIndex = sql.indexOf("select ");
            if (fieldStringBeginIndex >= 0x0) {
                int fieldStringEndIndex = sql.indexOf(" from ", fieldStringBeginIndex + 0x7);
                //System.out.println("getQueryFieldNamesFromSql fieldStringBeginIndex=" + fieldStringBeginIndex + " fieldStringEndIndex=" + fieldStringEndIndex);
                //System.out.println("getQueryFieldNamesFromSql fieldStringBeginIndex=" + sql.substring(fieldStringBeginIndex));
                //System.out.println("getQueryFieldNamesFromSql fieldStringEndIndex=" + sql.substring(fieldStringEndIndex));
                if (fieldStringEndIndex > fieldStringBeginIndex) {
                    String fieldString = sql.substring(fieldStringBeginIndex + 0x7, fieldStringEndIndex);
                    //System.out.println("getQueryFieldNamesFromSql fieldString=" + fieldString);
                    if (!StringUtils.isBlank(fieldString)) {
                        String[] fields = StringSplitUtil.Split(fieldString, ",", true);
                        if (fields != null && fields.length > 0x0) {
                            LinkedHashMap<String, String> result = new LinkedHashMap<>();

                            for (String str : fields) {
                                System.out.println("str=" + str);
                                //判断中间是否有as
                                //有一种使用格式转换的，如：CONVERT(varchar(100), INSERTTIME, 126) as INSERTTIME
                                int asIndex = str.indexOf(" as ");
                                if (asIndex > 0x0) {
                                    //String colName = str.substring(0x0, asIndex).trim().toLowerCase();
                                    //String aliName = str.substring(asIndex + 0x4).trim().toLowerCase();
                                    String colName = str.substring(0x0, asIndex).trim();
                                    String aliName = str.substring(asIndex + 0x4).trim();
                                    //有as时不去前面的，直接使用别名
                                    //result.put(colName, aliName);
                                    result.put(aliName, aliName);
                                } else {
                                    //没有as
                                    //result.put(str.toLowerCase(), null);
                                    result.put(str, null);
                                }
                            }

                            return result;
                        }
                    }
                }
            }
        }
        return null;
    }

    /***
     * 调用Platform中的接口获取指定数据库表的sql简要信息
     *
     * @param tableCode 数据库表编码
     * @return java.util.List<qc.module.platform.dto.dbTableSql.DBTableSqlSimpleDto>
     * @author QuCheng Tech
     * @since 2024/1/30
     */
    public DBTableFullDto getDbTableFullInfo(String tableCode) {
        ServiceInstance service = discoveryClient.getInstances("module-platform").get(0);
        String url = "http://" + service.getHost() + ":" + service.getPort() + "/dbtable/getfull/" + tableCode;

        HttpEntity<String> formEntity = new HttpEntity<String>(null, null);
        ParameterizedTypeReference<DBTableFullDto> responseType = new ParameterizedTypeReference<DBTableFullDto>() {
        };

        ResponseEntity<DBTableFullDto> response = restTemplate.exchange(
                url,//获取资源的地址
                HttpMethod.GET,
                formEntity,
                responseType
        );
        DBTableFullDto resultDto = response.getBody();

        return resultDto;
    }

    /***
     * 调用Platform中的接口获取指定数据库表的sql简要信息
     *
     * @param tableCode 数据库表编码
     * @return java.util.List<qc.module.platform.dto.dbTableSql.DBTableSqlSimpleDto>
     * @author QuCheng Tech
     * @since 2024/1/30
     */
    public List<DBTableSqlSimpleDto> getDbTableSqlSimples(String tableCode) {
        ServiceInstance service = discoveryClient.getInstances("module-platform").get(0);
        String url = "http://" + service.getHost() + ":" + service.getPort() + "/dbtablesql/tablesimple/" + tableCode;

        HttpEntity<String> formEntity = new HttpEntity<String>(null, null);
        ParameterizedTypeReference<List<DBTableSqlSimpleDto>> responseType = new ParameterizedTypeReference<List<DBTableSqlSimpleDto>>() {
        };

        ResponseEntity<List<DBTableSqlSimpleDto>> response = restTemplate.exchange(
                url,//获取资源的地址
                HttpMethod.GET,
                formEntity,
                responseType
        );
        List<DBTableSqlSimpleDto> resultDto = response.getBody();

        return resultDto;
    }

    /***
     * 调用platform中的接口获取指定数据库表指定sql的详细信息
     *
     * @param tableCode 数据库表编码
     * @param sqlCode sql代码
     * @return java.lang.String
     * @author QuCheng Tech
     * @since 2024/1/30
     */
    public String getDbTableSqlString(String tableCode, String sqlCode) {
        ServiceInstance service = discoveryClient.getInstances("module-platform").get(0);
        String url = "http://" + service.getHost() + ":" + service.getPort() + "/dbtablesql/get?tbcode=" + tableCode + "&code=" + sqlCode;

        HttpEntity<String> formEntity = new HttpEntity<String>(null, null);
        ParameterizedTypeReference<DBTableSqlDto> responseType = new ParameterizedTypeReference<DBTableSqlDto>() {
        };

        ResponseEntity<DBTableSqlDto> response = restTemplate.exchange(
                url,//获取资源的地址
                HttpMethod.GET,
                formEntity,
                responseType
        );
        DBTableSqlDto resultDto = response.getBody();

        return resultDto.getSqlstr();
    }

    /***
     * 调用Platform中的接口获取指定数据库表的显示列信息
     *
     * @param tableCode 数据库表编码
     * @return java.util.List<qc.module.platform.dto.dbTableSql.DBTableSqlSimpleDto>
     * @author QuCheng Tech
     * @since 2024/1/30
     */
    public List<DBTableColumnDto> getDbTableShowColumns(String tableCode) {
        ServiceInstance service = discoveryClient.getInstances("module-platform").get(0);
        String url = "http://" + service.getHost() + ":" + service.getPort() + "/dbtablecolumn/tableshow/" + tableCode;

        HttpEntity<String> formEntity = new HttpEntity<String>(null, null);
        ParameterizedTypeReference<List<DBTableColumnDto>> responseType = new ParameterizedTypeReference<List<DBTableColumnDto>>() {
        };

        ResponseEntity<List<DBTableColumnDto>> response = restTemplate.exchange(
                url,//获取资源的地址
                HttpMethod.GET,
                formEntity,
                responseType
        );
        List<DBTableColumnDto> resultDto = response.getBody();

        return resultDto;
    }

    /**
     * 根据token获取用户所在的有效的部门
     *
     * @param token 用户登录得到的Token
     * @return 部门简要信息集合
     * @author QuCheng Tech
     * @since 2023/5/31
     */
    public List<DeptSimpleDto> getUserValidDepts(String token) {
        HttpHeaders headers = new HttpHeaders();
        headers.add(QCAuthConstant.TOKEN_HEADER_KEY, token);

        ServiceInstance service = discoveryClient.getInstances("module-platform").get(0);
        String url = "http://" + service.getHost() + ":" + service.getPort() + "/dept/uservalid";

        HttpEntity<String> formEntity = new HttpEntity<String>(null, headers);
        ParameterizedTypeReference<List<DeptSimpleDto>> responseType = new ParameterizedTypeReference<List<DeptSimpleDto>>() {
        };

        ResponseEntity<List<DeptSimpleDto>> response = restTemplate.exchange(
                url,//获取资源的地址
                HttpMethod.GET,
                formEntity,
                responseType
        );
        List<DeptSimpleDto> resultDto = response.getBody();

        return resultDto;
    }
}
